using System;
using System.Collections.Generic;
using System.ComponentModel;
using Newtonsoft.Json.Linq;
using OpenTap.Plugins.Interfaces.Common;
using OpenTap.Plugins.Interfaces.PSU;

namespace OpenTap.Plugins.Psu
{
    /// <summary>
    /// Base class for PSU. Made basically for N5700 but as a base class
    /// to enable other drivers to call this and still call base.Open/base.Close properly
    /// </summary>
    public abstract class PsuBase : ScpiInstrument, IPsu
    {
        protected double MaxCurrentA;
        protected double MaxVoltageV;
        protected double MinCurrentA;
        protected double MinVoltageV;
        //N6700 modular power specific parameters.
        protected EModule SelectedN6700Module;
        protected bool ModulePowerInUse = false; //TODO: this likely problematic if two powers are connected ( N5700 and N6700 ) Has to be fixed!
        protected List<N6700Module> N6700Modules;
        // ReSharper disable once InconsistentNaming
        protected object _instrumentLock = new object();
        protected const int MinDelayIfPolarityChange = 500;

        public void Close(bool keepPowerOn)
        {
            Log.Info("PsuBase : Close");
            if (!keepPowerOn) SetOutputState(EState.Off);
            base.Close();
        }

        public override void Close()
        {
            Log.Info("PsuBase : Close");
            SetOutputState(EState.Off);
            base.Close();
        }

        /// <inheritdoc />
        public virtual string IdnModule(EModule moduleNumber, out int numberOfModules)
        {
            numberOfModules = 0;
            Log.Info("Command not supported with this equipment!");
            return   "Command not supported with this equipment!";
        }

        /// <inheritdoc />
        public virtual double MeasureCurrent(EModule module = EModule.Module1)
        {
            lock (_instrumentLock)
            {
                return ScpiQuery<double>(IsModularPowerInUse("MEAS:CURR?", module));
            }
        }

        /// <summary>
        /// For modular PSU's only. Adds right module target for Scpi commands.
        /// </summary>
        /// <param name="command">normal psu scpi command</param>
        /// <param name="currentEModule">target module</param>
        /// <returns>scpi command strig that has target info in it</returns>
        protected string FormatCommandToTargetModule(string command, EModule currentEModule)
        {
            string scpiCommand;

            switch (currentEModule)
            {
                case EModule.Module1:
                    scpiCommand = command + " (@1)";
                    break;
                case EModule.Module2:
                    scpiCommand = command + " (@2)";
                    break;
                case EModule.Module3:
                    scpiCommand = command + " (@3)";
                    break;
                case EModule.Module4:
                    scpiCommand = command + " (@4)";
                    break;
                default: //should not ever come, but developers are developers.
                    throw new Exception("Internal error! Unhandled case in PsuBase.FormatCommandToTargetModule method.");
            }
            Log.Debug(scpiCommand);
            return scpiCommand;
        }

        /// <summary>
        /// This method will check if N6700 power is in use. If N6700 is not used, scpi command will not be altered. If N6700 is found,
        /// it will modify max voltage and current settings according to which is currently selected module. Also target info will be concatenated
        /// in scpi command.
        /// </summary>
        /// <param name="scpiCommand">Assign scpi command fit for N5700, will be converted if needed.</param>
        /// <param name="module">Selected module.</param>
        /// <returns> Ready made scpi command for both n5700 or n6700</returns>
        protected string IsModularPowerInUse(string scpiCommand, EModule module = EModule.Module1)
        {
            //ModulePowerInUse will be set when initializing N6700 PSU.
            if (!ModulePowerInUse) return scpiCommand;

            var pusModule = N6700Modules.Find(x => x.ModuleId == module);
            MaxCurrentA = pusModule.MaxCurrentA;
            MaxVoltageV = pusModule.MaxVoltageV;
            Log.Debug("Module id: " + pusModule.ModuleId.ToString() + " changed. Changed Max Current to:" + MaxCurrentA + " and Max Voltage to:" + MaxVoltageV);
            return FormatCommandToTargetModule(scpiCommand, module);
        }

        /// <inheritdoc />
        public virtual double MeasureVoltage(EModule module = EModule.Module1)
        {
            lock (_instrumentLock)
            {
                return ScpiQuery<double>(IsModularPowerInUse("MEAS:VOLT?", module));
            }
        }

        /// <summary>
        /// Run set current command to PSU
        /// </summary>
        protected abstract void ScpiCmd_SetCurr(double currentAmps, EModule module = EModule.Module1);

        /// <inheritdoc />
        public virtual void SetCurrent(double currentAmps, EModule module = EModule.Module1)
        {
            SetModularPowerLimits(module);

            if (currentAmps > MaxCurrentA)
                throw new ArgumentException("Trying to set current too high!", "currentAmps");
            if (currentAmps < MinCurrentA)
                throw new ArgumentException("Trying to set current too low!", "currentAmps");

            ScpiCmd_SetCurr(currentAmps, module);
        }

        /// <inheritdoc />
        public abstract double GetCurrentLimit(EModule module = EModule.Module1);

        /// <inheritdoc />
        public virtual void SetCurrentProtectionState(EState state, EModule module = EModule.Module1)
        {
            if (!Enum.IsDefined(typeof(EState), state))
                throw new InvalidEnumArgumentException("state", (int)state, typeof(EState));

            lock (_instrumentLock)
            {
                ScpiCommand(IsModularPowerInUse(EState.On == state ? "CURR:PROT:STAT ON" : "CURR:PROT:STAT OFF"), module);
            }
        }

        /// <inheritdoc />
        public virtual EState GetCurrentProtectionStatus(EModule module = EModule.Module1)
        {
            lock (_instrumentLock)
            {
                // N5700 uses Questionable Event register's bit 1 to over current status
                var status = ScpiQuery<short>(IsModularPowerInUse("STAT:QUES:COND?", module)) & 2;
                return status == 0 ? EState.Off : EState.On;
            }
        }

        /// <inheritdoc />
        public virtual EState GetOutputState(EModule module = EModule.Module1)
        {
            lock (_instrumentLock)
            {
                var response = ScpiQuery("OUTP:STAT?");
                if (response.Contains("0"))
                    return (EState.Off);

                if (response.Contains("1"))
                    return (EState.On);

                throw new Exception("GetOutputState did not catch the wanted(0/1) answer from: " + response);
            }
        }

        /// <inheritdoc />
        public virtual void SetOutputState(EState state, EModule module = EModule.Module1)
        {
            if (!Enum.IsDefined(typeof(EState), state))
                throw new InvalidEnumArgumentException("state", (int)state, typeof(EState));

            lock (_instrumentLock)
            {
                ScpiCommand(IsModularPowerInUse(EState.On == state ? "OUTP:STAT ON" : "OUTP:STAT OFF", module));
            }
        }

        /// <inheritdoc />
        public virtual void ClearOutputProtection(EModule module = EModule.Module1)
        {
            lock (_instrumentLock)
            {
                SetOutputState(EState.Off, module);
                ScpiCommand(IsModularPowerInUse("OUTP:PROT:CLE", module));
            }
        }

        /// <summary>
        /// Run set voltage command to PSU
        /// </summary>
        protected abstract void ScpiCmd_SetVolt(double voltageVolts, EModule module = EModule.Module1);

        /// <inheritdoc />
        public virtual void SetVoltage(double voltageVolts, EModule module = EModule.Module1)
        {
            SetModularPowerLimits(module);

            if (voltageVolts > MaxVoltageV || voltageVolts < MinVoltageV)
                throw new ArgumentException("Trying to set voltage level out of limits!", "voltageVolts");
            lock (_instrumentLock)
            {
                ScpiCmd_SetVolt(voltageVolts, module);
            }
        }

        /// <inheritdoc />
        public abstract double GetVoltageLevel(EModule module = EModule.Module1);

        /// <inheritdoc />
        public abstract double GetVoltageLevelMax(EModule module = EModule.Module1);

        /// <inheritdoc />
        public abstract void SetOverVoltageLevel(double voltage, EModule module = EModule.Module1);

        /// <inheritdoc />
        public abstract double GetOverVoltageLevel(EModule module = EModule.Module1);

        /// <inheritdoc />
        public abstract double GetOverVoltageLevelMax(EModule module = EModule.Module1);

        /// <inheritdoc />
        public abstract void SetPolarity(EPolarity polarity, EModule module = EModule.Module1);

        /// <inheritdoc />
        public virtual EState GetDcFeedFaultStatus(EModule module = EModule.Module1)
        {
            lock (_instrumentLock)
            {
                const int inh = 512; // Bit nr 9
                var response = ScpiQuery<int>("STAT:QUES:EVEN?");
                return inh == (response & inh) ? EState.On : EState.Off;
            }
        }

        public void SelectPsuModule(EModule module)
        {
            var targetModule = N6700Modules.Find(x => x.ModuleId == module);
            if (!targetModule.ModuleInUse)
                throw new ApplicationException(module.ToString() + " not in use.");
            SelectedN6700Module = module;
        }


        /// <inheritdoc />
        public abstract void SetMeterView(EView view);

        private void SetModularPowerLimits(EModule module)
        {
            if (!ModulePowerInUse) return;

            var psuModule = N6700Modules.Find(x => x.ModuleId == module);
            MaxCurrentA = psuModule.MaxCurrentA;
            MaxVoltageV = psuModule.MaxVoltageV;
        }

        /// <inheritdoc />
        public void ModularPower_DefineGroups(string channelList)
        {
            lock (_instrumentLock)
            {
                string groupCommand = "SYST:GRO:DEF " + channelList;
                ScpiCommand(groupCommand);
            }
        }
        /// <inheritdoc />
        public void ModularPower_DeleteGroups()
        {
            lock (_instrumentLock)
            {
                ScpiCommand("SYST:GRO:DEL:ALL");
            }
        }

        /// <inheritdoc />
        public string ModularPower_CatalogOfGroupedChannels()
        {
            lock (_instrumentLock)
            {
                return ScpiQuery("SYST:GRO:CAT? ");
            }
        }

        /// <inheritdoc />
        public int SelfTest(out string message)
        {
            var code = ScpiQuery<int>("*TST?");

            message = code == 0 ? "SelfTest passed." : ScpiQuery("SYST:ERR?");

            return code;
        }

        public bool IsModular()
        {
            throw new NotImplementedException();
        }

        public void Initialize(JObject configElement)
        {
            throw new NotImplementedException();
        }

        public void Clear()
        {
            throw new NotImplementedException();
        }
    }

    /// <summary>
    /// Used to store max AMP and VOLT values from modular PSU.
    /// </summary>
    public class N6700Module
    {
        public EModule ModuleId { get; protected set; }
        public bool ModuleInUse { get; protected set; }
        public double MaxCurrentA { get; protected set; }
        public double MaxVoltageV { get; protected set; }
        //TODO N6700 supports grouped modules, add support here if it's needed later.

        /// <summary>
        ///  Defines new module for N6700
        /// </summary>
        /// <param name="moduleId">Current module slot id, can range from 1 -4</param>
        /// <param name="moduleInUse">Inform if module is in use or not.</param>
        /// <param name="maxCurrent">Modules max current, varies from module to module</param>
        /// <param name="maxVoltage">Modules max voltage, also varies.</param>
        public N6700Module(EModule moduleId, bool moduleInUse, double maxCurrent, double maxVoltage)
        {
            ModuleId = moduleId;
            ModuleInUse = moduleInUse;
            MaxCurrentA = maxCurrent;
            MaxVoltageV = maxVoltage;
        }
    }
}